package com.hqd.ch03.v50.boot;

import com.hqd.ch03.v50.beans.BeansException;
import com.hqd.ch03.v50.beans.factory.NoSuchBeanDefinitionException;
import com.hqd.ch03.v50.beans.factory.SmartInitializingSingleton;
import com.hqd.ch03.v50.beans.factory.config.BeanDefinition;
import com.hqd.ch03.v50.beans.factory.config.BeanFactoryPostProcessor;
import com.hqd.ch03.v50.beans.factory.config.ConfigurableListableBeanFactory;
import com.hqd.ch03.v50.beans.factory.support.AbstractBeanDefinition;
import com.hqd.ch03.v50.core.Ordered;

import java.util.ArrayList;
import java.util.Collection;

public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Collection<LazyInitializationExcludeFilter> filters = getFilters(beanFactory);
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            if (beanDefinition instanceof AbstractBeanDefinition) {
                postProcess(beanFactory, filters, beanName, (AbstractBeanDefinition) beanDefinition);
            }
        }
    }

    private Collection<LazyInitializationExcludeFilter> getFilters(ConfigurableListableBeanFactory beanFactory) {
        // Take care not to force the eager init of factory beans when getting filters
        ArrayList<LazyInitializationExcludeFilter> filters = new ArrayList<>(
                beanFactory.getBeansOfType(LazyInitializationExcludeFilter.class, false, false).values());
        filters.add(LazyInitializationExcludeFilter.forBeanTypes(SmartInitializingSingleton.class));
        return filters;
    }

    private void postProcess(ConfigurableListableBeanFactory beanFactory,
                             Collection<LazyInitializationExcludeFilter> filters, String beanName,
                             AbstractBeanDefinition beanDefinition) {
        Boolean lazyInit = beanDefinition.getLazyInit();
        if (lazyInit != null) {
            return;
        }
        Class<?> beanType = getBeanType(beanFactory, beanName);
        if (!isExcluded(filters, beanName, beanDefinition, beanType)) {
            beanDefinition.setLazyInit(true);
        }
    }

    private Class<?> getBeanType(ConfigurableListableBeanFactory beanFactory, String beanName) {
        try {
            return beanFactory.getType(beanName, false);
        } catch (NoSuchBeanDefinitionException ex) {
            return null;
        }
    }

    private boolean isExcluded(Collection<LazyInitializationExcludeFilter> filters, String beanName,
                               AbstractBeanDefinition beanDefinition, Class<?> beanType) {
        if (beanType != null) {
            for (LazyInitializationExcludeFilter filter : filters) {
                if (filter.isExcluded(beanName, beanDefinition, beanType)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

}

